// INCLUDES -------------------------------------------------------------------------
#include "raytracer.h"	// project

#include "stdio.h"		// printf()
#include "string.h"		// mem[set/cpy/cmp]()
#include "stdlib.h"		// calloc/free

// VARIABLE DECLARATIONS ------------------------------------------------------------

typedef enum
{
	MAX_STACK_SIZE = 1000,
	MAX_PIXEL_SIZE = 4096
} framebufferLimits;

framebuffer fStack[MAX_STACK_SIZE];		// stack of single framebuffers

bool stackIsReady = false;				// initialization flag for framebuffer stack
int currentStackPosition = 0;			// stack position for framebufferCreate()

// FUNCTIONS ------------------------------------------------------------------------

void framebufferInitStack()
{
	// depending on the state of the stack, check if it needs to be initalized
	if (stackIsReady == true) return; // NO, it doesn't...

	// YES, it does! loop through all the stack positions and initialize them
	int i;
	for (i = 0; i < MAX_STACK_SIZE; i++)
	{
	  fStack[i].type = RGBA32;	// RGB8 standard type
	  fStack[i].buffer = NULL;	// invalid buffer
	  fStack[i].width = -1;		// invalid width
	  fStack[i].height = -1;	// invalid height
	}
	stackIsReady = true;  // mark init "done"
};

void framebufferFreeMemory()
{
	int i;
	for (i = 0; i < currentStackPosition; i++)
	{
	  if (fStack[currentStackPosition].buffer != NULL)	// check if memory is occupied
	  {
			free(fStack[currentStackPosition].buffer);	// release memory
		};
 	}
	currentStackPosition = 0;	// start allocating from position 0
	stackIsReady = false;  		// mark release "done"

	framebufferInitStack();		// re-init to startup state
}

int framebufferCreate(framebufferType type,	// type of framebuffer data
					  unsigned int width,	// width in pixels X
					  unsigned int height)	// height in pixels Y
{
	// fail if:
	ASSERT_GUARD(currentStackPosition+1 < MAX_STACK_SIZE &&		// slot position is invalid OR              -> stack position
				 (unsigned int)type < FRAMEBUFFERTYPE_EOL &&	// type is end/larger than list  OR         |<-
				 width != 0 &&									// width is less than 1 pixel OR            -> framebuffer dimensions
				 height != 0 &&									// height is less than 1 pixel OR           |
				 width <= MAX_PIXEL_SIZE &&						// width is larger than max pixel size OR   |
				 height <= MAX_PIXEL_SIZE,						// height is larger than max pixel size     |<-
				 "type:%i, width:%i, height:%i",
				 type, width, height);
				 
	// ensure a proper framebuffer stack initialization
	framebufferInitStack();

	// now, create the framebuffer...

	// save type of buffer and sizes
	fStack[currentStackPosition].type = type;
	fStack[currentStackPosition].width = width;
	fStack[currentStackPosition].height = height;
	
	// calculate byte-size of entire buffer
	int byteSize = -1;
	switch (type)
	{
		case RGBA32: byteSize = sizeof(pixelRGBA32);	break;
	};
	
	fStack[currentStackPosition].buffer = calloc(width*height, byteSize);  	// allocate memory for framebuffer
	NULL_GUARD(fStack[currentStackPosition].buffer);
	
	return currentStackPosition++; // success!
};

// register a function callback for framebufferProcess() to work with
void framebufferRegisterCallback(unsigned int slot,       // slot id of an existing framebuffer
                                 framebufferCallback cb)  // callback function for framebufferProcess()
{
	// fail if:
	ASSERT_GUARD(slot < currentStackPosition &&  // slot id isn't in the range of [0, existing framebuffers-1]
							 cb != NULL,                      // callback address is NULL
							 "slot:%i, cb:0x%x",
							 slot, cb);

	fStack[slot].cb = cb; // save callback function address
};

// process the framebuffer and call a registered callback
void framebufferProcess(unsigned int slot,
                        processingType type)
{
	// fail if:
	ASSERT_GUARD(slot < currentStackPosition &&  					// slot id isn't in the range of [0, existing framebuffers-1]
							 (unsigned int)type < PROCESSINGTYPE_EOL, // processingType is unknown
							 "slot:%i, type:%i",
							 slot, type);

	// now execute the right processing type...
	switch(type)
	{
		// -------------------------------------------------------------------------------------------------------
		// [for every line -> for every pixel of that line] top-to-bottom
		case LINE:
		{
			unsigned int line,  				// current line (Y)
									 pixel, 				// current pixel (X)
									 position = 0;  // calculated array position in framebuffer

			for (line = 0; line < fStack[slot].height; line++) 			// for every line (top-to-bottom)...
			{
			  for (pixel = 0; pixel < fStack[slot].width; pixel++)	// for every pixel (left-to-right)...
			  {
			    // calculate position in RGBA framebuffer of (x,y)
          position = line * fStack[slot].width + pixel;
					// get pixel array base and execute callback
					pixelRGBA32* p = fStack[slot].buffer;
					fStack[slot].cb(pixel, line, &p[position]);
			  }
	 		}
		};
    // -------------------------------------------------------------------------------------------------------
	};

};

// retrieve raw access to a framebuffer that was previously created
framebuffer* framebufferGetFramebuffer(unsigned int slot)
{
	// fail if:
	ASSERT_GUARD(slot <= currentStackPosition, // slot is larger than amount of available framebuffer OR
	             "slot:%i",
				 slot);

	return &fStack[slot];
};
